package io.hellsinger.filesystem.path

import kotlin.math.min

abstract class PathWrapper(
    final override val bytes: ByteArray,
) : Path {
    protected val separators = arrayListOf<Int>()

    private var cachedPath: String? = null
    private var cachedHash: Int? = null

    override val nameCount: Int
        get() = separators.size

    override val length: Int
        get() = bytes.size

    override val isEmpty: Boolean
        get() = bytes.isEmpty()

    override val parent: Path?
        get() = getParentAt()

    override fun toString(): String {
        if (cachedPath == null) {
            cachedPath = bytes.decodeToString()
        }
        return cachedPath!!
    }

    override fun getParentAt(index: Int): Path? {
        if (index < 0 || index >= separators.size) return null
        return subPath(0, separators[index])
    }

    override fun iterator() =
        iterator {
            for (point in separators) yield(getNameAt(point))
        }

    override fun plus(path: Path): Path = resolve(path)

    override fun getNameAt(index: Int): Path {
        if (nameCount == 0) return onCreatePath(byteArrayOf(separator))

        val begin = separators[index] + 1
        val end = separators.getOrElse(index = index + 1) { length }

        return subPath(begin, end)
    }

    override fun relativize(other: Path): Path {
        if (isEmpty) return other
        if (this == other) onCreatePath(ByteArray(0))

        val size = length
        val otherSize = other.length
        val min = min(size, otherSize)
        var common = 0

        while (common < min && bytes[common] == other.bytes[common]) ++common

        val segments = mutableListOf<Byte>()

        if (common < otherSize) segments += other.bytes.toList().subList(common, otherSize)

        return onCreatePath(segments.toByteArray())
    }

    override fun subPath(
        from: Int,
        to: Int,
    ): Path {
        if (from == 0 && to == length) return this

        return onCreatePath(bytes.copyOfRange(from, to))
    }

    override fun resolve(other: Path): Path {
        if (isEmpty) return other

        if (other.isAbsolute) return other

        if (other.isEmpty) return this

        val path: ByteArray
        if (length == 1 && bytes[0] == separator) {
            path = ByteArray(other.bytes.size + 1)
            path[0] = separator
            other.bytes.copyInto(
                destination = path,
                destinationOffset = 1,
                endIndex = other.bytes.size,
            )
        } else {
            path = ByteArray(length + 1 + other.bytes.size)
            bytes.copyInto(path)
            path[bytes.size] = separator
            other.bytes.copyInto(
                destination = path,
                destinationOffset = length + 1,
                endIndex = other.bytes.size,
            )
        }

        return onCreatePath(path)
    }

    override fun resolve(other: ByteArray): Path {
        if (isEmpty) return onCreatePath(other)
        if (other.isEmpty()) return this

        val path: ByteArray
        if (length == 1 && bytes[0] == separator) {
            path = ByteArray(other.size + 1)
            path[0] = separator
            other.copyInto(
                destination = path,
                destinationOffset = 1,
                endIndex = other.size,
            )
        } else {
            path = ByteArray(length + 1 + other.size)
            bytes.copyInto(path)
            path[bytes.size] = separator
            other.copyInto(
                destination = path,
                destinationOffset = length + 1,
                endIndex = other.size,
            )
        }

        return onCreatePath(path)
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other is Path) return bytes.contentEquals(other.bytes)
        if (other is PathOwner<*>) return bytes.contentEquals(other.path.bytes)

        return false
    }

    override fun hashCode(): Int {
        if (cachedHash == null) {
            cachedHash = bytes.contentHashCode()
        }
        return cachedHash!!
    }

    override fun startsWith(other: Path): Boolean {
        for (index in 0 until other.bytes.size) {
            if (bytes[index] != other.bytes[index]) return false
        }
        return true
    }

    override fun endsWith(other: Path): Boolean {
        for (index in other.bytes.lastIndex downTo 0) {
            if (bytes[index] != other.bytes[index]) return false
        }
        return true
    }

    override fun compareTo(other: Path): Int = other.length - length

    protected abstract fun onCreatePath(path: ByteArray): Path
}
